package com.link.gateway.core.filter.global;

import com.link.gateway.base.allowurl.service.AllowUrlService;
import com.link.gateway.base.auth.model.Authclient;
import com.link.gateway.base.user.model.User;
import com.link.gateway.core.cllog.LCLogger;
import com.link.gateway.core.comproperty.*;
import com.link.gateway.core.utils.*;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author guoshan
 * @date 2019年07月18日
 * 生成内部token
 * 校验token
 */
@Component
public class LinkTokenGlobalFilter implements GlobalFilter, Ordered {


    @Resource
    private AntPathMatcher pathMatcher;

    @Resource
    private AllowUrlService allowUrlService;

    /**
     * 存储不拦截URL。
     */
    private static Set<String> urlSet = new HashSet<>();

    /**
     * 排除那些不需要校验的token，校验token，通过后生成内部token放在header中,响应时移除内部token。
     *
     * @param exchange
     * @param chain
     * @return
     */

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //跳过检测
        Object ignoreGlobalFilter = exchange.getAttribute(OtherConstants.ATTRIBUTE_IGNORE_GLOBAL_FILTER);
        if (ignoreGlobalFilter != null && (boolean)ignoreGlobalFilter) {
            return chain.filter(exchange);
        }

        long start = System.currentTimeMillis();
        //获取request、response
        ServerHttpRequest request = exchange.getRequest();
        //判断是否需要校验,不需要则生成内部token，然后放行
        URI uri = request.getURI();
        String path = uri.getPath();

        HttpHeaders reqHeaders = request.getHeaders();
        boolean interceptorFlag = true;
        ServerWebExchange build = exchange;
        long startMatchDefaultUrl = System.currentTimeMillis();
        for (String url : urlSet) {
            //判断请求url是否需要拦截，如果不需要则添加不拦截标识
            if (pathMatcher.match(url, path)) {
                //向headers中放文件，记得build
                ServerHttpRequest request1 = exchange.getRequest();
                ServerHttpRequest.Builder mutateR = request1.mutate();

                ServerHttpRequest host = exchange.getRequest().mutate()
                        .header(TokenConstants.NO_INTERCEPT_FLAG_NAME, TokenConstants.NO_INTERCEPT_FLAG_VALUE).build();
                //将现在的request 变成 change对象
                build = exchange.mutate().request(host).build();
                interceptorFlag = false;
                break;
            }
        }
        long  endMatchDefaultUrl = System.currentTimeMillis();
        FunctionTimeUtil.functionTime("LinkTokenGlobalFilter", "LinkTokenGlobalFilter1", startMatchDefaultUrl, endMatchDefaultUrl, false);

        if (interceptorFlag) {
            //获取当前请求的类型
            String type = TokenUtils.getTokenHeader(request);
            if (TokenConstants.UNKNOWN_REQ_TYPE.equals(type)) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.TOKEN_OVER, "没有传递token", "Y", exchange, HttpStatus.UNAUTHORIZED);
            }

            // bearer token值
            String tokenMsg = reqHeaders.getFirst(type);
            if ("null".equals(tokenMsg) || StringUtils.isBlank(tokenMsg)) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.TOKEN_OVER,
                        "传入的token格式有误！", "Y", exchange, HttpStatus.UNAUTHORIZED);
            }

            long startVerifyToken = System.currentTimeMillis();
            //如果没有匹配排除的URL，则校验的token，通过则放行，反之直接返回401
            boolean verifyResult = false;
            try {
                //如果认证信息包含在Authorization
                if (TokenConstants.AUTH_HEADER_NAME.equals(type)) {
                    User userCache = CacheUtil.checkAndCacheToken(tokenMsg);
                    if (userCache != null) {
                        RunProperties.SCOPE.set(userCache.getCurScope());
                        RunProperties.CURRENT_CLIENT_ID.set(userCache.getCurClientId());
                        exchange.getAttributes().put(OtherConstants.TOKEN_SCOPE, userCache.getCurScope());
                        exchange.getAttributes().put(OtherConstants.CURRENT_CLIENT_ID, userCache.getCurClientId());
                        verifyResult = true;
                    }
                } else {
                    //认证信息包含在APIAuthorization,此时应该按照api公钥模式去校验token
                    String token = TokenUtils.getToken(tokenMsg);
                    verifyResult = TokenUtils.verifyApiToken(token, path);
                }
            } catch (Exception e) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.TOKEN_OVER, e.getMessage(), "Y", exchange, HttpStatus.INTERNAL_SERVER_ERROR);

            }
            long endVerifyToken = System.currentTimeMillis();
            FunctionTimeUtil.functionTime("LinkTokenGlobalFilter", "LinkTokenGlobalFilter2", startVerifyToken, endVerifyToken, false);

            if (!verifyResult) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.TOKEN_OVER, "token校验失败", "Y", exchange, HttpStatus.UNAUTHORIZED);
            }

            //生成内部header
            Map<String, Object> innerToken;
            try {
                String token = TokenUtils.getToken(tokenMsg);
                innerToken = TokenUtils.genInnerToken(token, type,IpUtil.getIpAddress(request), path);
                exchange.getAttributes().put(OtherConstants.CURRENT_USER,RunProperties.TEMP_USER.get());
                //给接口访问信息参数添加用户信息
            } catch (IllegalAccessException illeagaException) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.TOKEN_OVER,
                        illeagaException.getMessage(), "Y", exchange, HttpStatus.FORBIDDEN);
            } catch (Exception e) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.TOKEN_OVER, e.getMessage(), "Y", exchange, null);
            }

            try {
                //防止数据篡改
                dataCheck(exchange);
            } catch (Exception e) {
                return ResponseDataUtils.writeWith(InterfaceLogConstants.DATA_CHANGE, e.getMessage(), "Y", exchange, null);
            }

            ServerHttpRequest req = null;

            //如果是api公私钥模式，则需要模拟正常请求生成token，存储在Redis中
            if (TokenConstants.API_AUTH_HEADER_NAME.equals(type)) {
                String userJson = (String) innerToken.get(TokenConstants.USER_JSON_KEY);
                String entJson = (String) innerToken.get(TokenConstants.ENTERPRISE_JSON_KEY);
                String customTokenKey = TokenUtils.genCustomToken(userJson, entJson);

                //如果是api 公钥的模式，需要将内部token和自定义token的键添加到header中，以便于存储到SecurityInterceptor的token中
                exchange.getRequest().mutate().header(TokenConstants.INNER_TOKEN, (String) innerToken.get(TokenConstants.INNER_TOKEN))
                        .header(TokenConstants.CUSTOM_TOKEN_KEY, customTokenKey).build();
            } else {
                //如果是非api 公钥的模式，只需要需要将内部token添加到请求头上即可
                exchange.getRequest().mutate().header(TokenConstants.INNER_TOKEN, (String) innerToken.get(TokenConstants.INNER_TOKEN)).build();

            }
            //将现在的request 变成 change对象
            build = exchange.mutate().request(req).build();
        }
        FunctionTimeUtil.functionTime("LinkTokenGlobalFilterPre", "LinkTokenGlobalFilter3", start, System.currentTimeMillis(), false);
        //设置正常通过校验后的返回信息
        return chain.filter(build).then(Mono.fromRunnable(() -> {
            //记录时间日志
            long end = System.currentTimeMillis();
            FunctionTimeUtil.functionTime("LinkTokenGlobalFilterPost", "LinkTokenGlobalFilter3", start, end, false);
        }));
    }


    @Override
    public int getOrder() {
        return FilterOrderConstants.TOKEN_CHECK_ORDER;
    }

    public void setUrlSet() throws Exception {
        List<String> urlList = allowUrlService.queryAllUrl();
        LinkTokenGlobalFilter.urlSet.clear();
        LinkTokenGlobalFilter.urlSet.addAll(urlList);
    }

    /**
     * 防止数据篡改
     * @param exchange
     * @return
     */
    private void dataCheck(ServerWebExchange exchange) throws Exception {
        try {
            ServerHttpRequest request = exchange.getRequest();
            //数据加密是以包体信息使用MD5加密得到第一个密文串，接上_当前日期使用AES加密
            HttpHeaders reqHeaders = request.getHeaders();
            String encryptData = reqHeaders.getFirst("EncryptData");
            User user = RunProperties.TEMP_USER.get();
            // 获取当前渠道,对body数据进行校验
            String clientId = RunProperties.CURRENT_CLIENT_ID.get();
            // 通过渠道查询aes密钥和校验开关信息
            // 查询渠道走内存
            Authclient authclient = CacheUtil.getAuthclientCache(clientId);
            String checkFlag = authclient.getCheckFlag();
            String body = exchange.getAttribute(ReqAndRespConstants.CACHE_REQUEST_BODY_KEY);
            LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("获取body：" + body).flush();
            LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("获取checkFlag：" + checkFlag).flush();

            boolean headerNotExsitsFlag = ("".equals(encryptData) || encryptData == null);
            if ("Y".equals(checkFlag) && headerNotExsitsFlag) {
                LCLogger.error().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("没有传入校验数据头！").flush();
                throw new Exception("没有传入校验数据头！");
            }

            if ("Y".equals(checkFlag)) {
                LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("body数据校验开始===========").flush();
                // 解密密钥
                String fixdKey = authclient.getAesKey();
                LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("获取密钥：" + fixdKey).flush();
                String dynamicAeskey = "";
                String userDynamicAeskey = user.getDynamicAeskey();
                if (user != null && StringUtils.isNotBlank(userDynamicAeskey)) {
                    dynamicAeskey = userDynamicAeskey;
                }

                String aesKey = fixdKey.substring(0, 8).concat(dynamicAeskey);
                LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("获取秘钥：" + aesKey).flush();
                // 数据解密
                String decrypData = AesUtil.decryptToBase64(encryptData, aesKey);
                //获取时间
                String[] str = decrypData.split("_");
                if (str.length < 3) {
                    LCLogger.error().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("校验数据有误").flush();
                    throw new Exception("数据有误");
                }
                String mpTime = str[1];
                //调用传入的url
                String callUrl = str[2];
                //当前实际的url
                String curUrl = exchange.getAttribute("requestPath");
                if (!curUrl.endsWith(callUrl)) {
                    LCLogger.error().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("传入的URL：" + callUrl).flush();
                    LCLogger.error().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("实际URL：" + curUrl ).flush();
                    throw new Exception("传入的url和实际url不匹配！");
                }
                //获取系统参数，判断时间差，系统参数以秒为单位
                String diff = "1200";
                LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("获取时间校验参数：" + diff).flush();
                if (DateUtil.getCurrentLongTime() > DateUtil.addSecondToDate(
                        DateUtil.stringToDate(mpTime, "yyyy-MM-dd HH:mm:ss"), Integer.valueOf(diff)).getTime()) {
                    LCLogger.error().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("校验超时，请检查").flush();
                    throw new Exception("校验超时，请检查");
                }
                //判断当前，如果是文件上传同时传入的加密body字符串为空，直接略过
                List<String> headers = request.getHeaders().get("Content-Type");
                if (headers != null && headers.size() > 0) {
                    for (String header : headers) {
                        if (header.startsWith("multipart/form-data")) {
                            body = "multipart/form-data";
                        }
                    }
                }
                //比较md5加密数据
                //md5加密是使用body数据进行md5加密
                if (StringUtils.isNotBlank(body)) {
                    String pwd = PasswordUtil.md5(body);
                    if (!pwd.equals(str[0])) {
                        LCLogger.error().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("数据校验错误，请检查").flush();
                        throw new Exception("数据校验错误，请检查");
                    }
                }
                LCLogger.info().withMessageKey("LinkTokenGlobalFilter.dataCheck").withMessage("body数据校验结束===========").flush();
            }
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }
}
